﻿/*******************************************************************************

    Units of Measure for C# applications

    Copyright (C) 2013 Marek Aniola

    This program is provided to you under the terms of the license
    as published at http://unitsofmeasure.codeplex.com/license


********************************************************************************/
using System;

namespace Man.UnitsOfMeasure
{
	public class Measure<T> : IEquatable<Measure<T>> where T : struct
	{
		#region Properties

		public Dimension Sense { get; private set; }
		public T Scale { get; private set; }
		public T Shift { get; private set; }

		#endregion Properties

		#region Constructor(s)

		public Measure(T scale, Dimension sense, T shift)
		{
			if (TypeHost.Equal(scale, TypeHost.Zero))
				throw new ArgumentException("Scale for the unit of measure must no be zero");

			Scale = scale;
			Shift = shift;
			Sense = sense;
		}

		public Measure(T scale, Dimension sense) :
			this(scale, sense, TypeHost.Zero)
		{
		}

		public Measure(Dimension sense) :
			this(TypeHost.Unity, sense, TypeHost.Zero)
		{
		}

		public Measure(T scale, Magnitude magnitude, T shift) :
			this(scale, new Dimension(magnitude), shift)
		{
		}

		public Measure(T scale, Magnitude magnitude) :
			this(scale, new Dimension(magnitude), TypeHost.Zero)
		{
		}

		// Base units (scale = 1.0, shift = 0.0)
		public Measure(Magnitude magnitude) :
			this(TypeHost.Unity, new Dimension(magnitude), TypeHost.Zero)
		{
		}

		#endregion Constructor(s)

		#region IEquatable<Measure<T>>

		public bool Equals(Measure<T> other)
		{
			return !object.Equals(other, null) && 
				TypeHost.Equal(Scale, other.Scale) && (Sense == other.Sense) && TypeHost.Equal(Shift, other.Shift);
		}

		public override bool Equals(object obj)
		{
			return (obj != null) && (obj is Measure<T>) && this.Equals((Measure<T>)obj);
		}

		public static bool operator == (Measure<T> x, Measure<T> y)
		{
			return object.ReferenceEquals(x, y) || (!object.Equals(x, null) && x.Equals(y));
		}

		public static bool operator != (Measure<T> x, Measure<T> y)
		{
			return !(x == y);
		}

		public override int GetHashCode()
		{
			unchecked
			{
				int hash = 17;
				hash = hash * 23 + Scale.GetHashCode();
				hash = hash * 23 + Sense.GetHashCode();
				hash = hash * 23 + Shift.GetHashCode();
				return hash;
			}
		}

		#endregion IEquatable<Measure<T>>

		#region Scale / Shift operators

		public static Measure<T> operator * (T scale, Measure<T> unit)
		{
			return new Measure<T>(TypeHost.Multiply(scale, unit.Scale), unit.Sense, TypeHost.Multiply(scale, unit.Shift));
		}

		public static Measure<T> operator * (Measure<T> unit, T scale)
		{
			return new Measure<T>(TypeHost.Multiply(scale, unit.Scale), unit.Sense, TypeHost.Multiply(scale, unit.Shift));
		}

		public static Measure<T> operator / (Measure<T> unit, T scale)
		{
			return new Measure<T>(TypeHost.Divide(unit.Scale, scale), unit.Sense, TypeHost.Divide(unit.Shift, scale));
		}

		public static Measure<T> operator + (Measure<T> unit, T shift)
		{
			return new Measure<T>(unit.Scale, unit.Sense, TypeHost.Add(unit.Shift, shift));
		}

		public static Measure<T> operator + (T shift, Measure<T> unit)
		{
			return new Measure<T>(unit.Scale, unit.Sense, TypeHost.Add(unit.Shift, shift));
		}

		public static Measure<T> operator - (Measure<T> unit, T shift)
		{
			return new Measure<T>(unit.Scale, unit.Sense, TypeHost.Subtract(unit.Shift, shift));
		}

		#endregion Scale / Shift operators

		#region Multiply / Divide operators

		public static Measure<T> operator * (Measure<T> a, Measure<T> b)
		{
			return new Measure<T>(TypeHost.Multiply(a.Scale, b.Scale), a.Sense * b.Sense, TypeHost.Zero);
		}

		public static Measure<T> operator / (Measure<T> a, Measure<T> b)
		{
			return new Measure<T>(TypeHost.Divide(a.Scale, b.Scale), a.Sense / b.Sense, TypeHost.Zero);
		}

		#endregion Multiply / Divide operators

		#region Basic math

		public static Measure<T> Pow(Measure<T> a, int num, int den)
		{
			return new Measure<T>(TypeHost.Pow(a.Scale, num, den), Dimension.Pow(a.Sense, num, den), TypeHost.Zero);
		}

		public static Measure<T> Pow(Measure<T> a, int num)
		{
			return Pow(a, num, 1);
		}

		public static Measure<T> Sqrt(Measure<T> a)
		{
			return Pow(a, 1, 2);
		}

		#endregion Basic math

		#region Conversions

		public Quantity<T> this[T quantity]
		{
			get { return new Quantity<T>(quantity, this); }
		}

		// quantity[base unit] = quantity[this unit] / this.Scale
		// quantity[this unit] = quantity[base unit] * this.Scale
		public Quantity<T> this[Quantity<T> quantity]
		{
			get { return new Quantity<T>(quantity /this, this); }
		}

		public Level<T> Level(T level)
		{
			return new Level<T>(level, this);
		}

		// level[base unit] = (level[this unit] - this.Shift) / this.Scale
		// level[this unit] = level[base unit] * this.Scale + this.Shift
		public Level<T> this[Level<T> level]
		{
			get { return new Level<T>(level / this, this); }
		}

		#endregion Conversions

		#region Dimensional analysis

		public static bool Convertible(Measure<T> a, Measure<T> b)
		{
			return (a.Sense == b.Sense);
		}

		#endregion Dimensional analysis

		#region Formatting

		public override string ToString()
		{
			return ToString(null);
		}

		public virtual string ToString(IFormatProvider fp)
		{
			return (TypeHost.Equal(Shift, TypeHost.Zero) ? 
				String.Format(fp, "[{0}*({1})]", Scale, Sense) :
				String.Format(fp, "[{0}*({1})+{2}]", Scale, Sense, Shift));
		}

		#endregion Formatting

		#region Statics

		private static ITypeHost<T> TypeHost = InitializeTypeHost();

		internal static ITypeHost<T> InitializeTypeHost()
		{
			Type genericType = typeof(T);
			if (genericType == typeof(double))
				return (ITypeHost<T>)(new HostDouble());
			else if (genericType == typeof(decimal))
				return (ITypeHost<T>)(new HostDecimal());
			else if (genericType == typeof(float))
				return (ITypeHost<T>)(new HostFloat());
			else
			{
				throw new NotImplementedException(String.Format("Not implemented \"{0}\" type host.", genericType.Name));
			}
		}

		#endregion
	}
}
